<?php
/**
 * crcl/base
 *
 * Copyright 2013 CRCL - André Sabosch - info@crcl.de
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Creating multiton pattern
 */

namespace crcl\base\instance;

interface IMultiton
{
  /**
   * Add Uri to Alias Cache
   *
   * @static
   * @param string  $sUri   URI
   * @param string  $sAlias Alias
   */
  public static function prepare($sUri, $sAlias);

  /**
   * Returns Instance from URI or Alias
   * uses sFactoryClass (default: child) and sFactoryDirectory (default: container),
   * if defined in child class
   *
   * @static
   * @param string  $sIdent URI or Alias
   *
   * @return $oInstance
   */
  public static function getInstance($sIdent);

  /**
   * Alias to getInstance($sIdent)
   *
   * @static
   * @param string  $sIdent URI or Alias
   *
   * @return $oInstance
   */
  public static function multiton($sIdent);

  /**
   * Removes cached instance
   *
   * @static
   * @param string $sIdent URI or Alias
   */
  public static function removeInstance($sIdent);

  /**
   * Checks if instance if cached
   *
   * @static
   * @param string $sIdent URI or Alias
   * @return bool
   */
  public static function isInstance($sIdent);
}

abstract class Multiton extends Factory implements IMultiton
{
  public static function prepare($sUri, $sAlias)
  {
    $sClass = \get_called_class();
    self::$aCache[$sClass][$sAlias] = $sUri;
  }

  public static function getInstance($sIdent)
  {
    return self::multiton($sIdent);
  }

  public static function multiton($sIdent)
  {
    $key = self::getKeyFromIdent($sIdent);
    $child = \get_called_class();

    if (!isset(self::$aInstances[$child][$key]))
    {
      $sClass = self::getFactoryClass();

      //triggers external or internal factory
      self::$aInstances[$child][$key] = $sClass::factory(self::$aCache[$child][$key]);
    }
    return self::$aInstances[$child][$key];
  }

  public static function removeInstance($sIdent)
  {
    $sAlias = self::getKeyFromIdent($sIdent);
    $sClass = \get_called_class();
    unset(self::$aInstances[$sClass][$sAlias]);
  }

  public static function isInstance($sIdent)
  {
    $sAlias = self::getKeyFromIdent($sIdent);
    $sClass = \get_called_class();

    $bReturn = \FALSE;
    if (isset(self::$aInstances[$sClass][$sAlias]))
    {
      $bReturn = \TRUE;
    }
    return $bReturn;
  }

  //could be private, but overridden from factory
  public static function factory($sUri)
  {
    $sType = self::checkUri($sUri);
    $sDirectory = self::getFactoryDirectory();

    $sNamespace = self::getNamespaceFromCalledClass();
    $sClass = $sNamespace . '\\' . $sDirectory . '\\' . \ucfirst($sType);

    $oInstance = new $sClass($sUri);
    return $oInstance;
  }

  /** PROTECTED ***********************************************************************************/

  private static $aInstances = array();
  private static $aCache = array();

  private static function getKeyFromIdent($sIdent)
  {
    //ident = alias
    $sClass = \get_called_class();
    if (isset(self::$aCache[$sClass][$sIdent]))
    {
      return $sIdent;
    }

    //ident = uri
    if (isset(self::$aCache[$sClass]))
    {
      $key = \array_search($sIdent, self::$aCache[$sClass]);
      if ($key)
      {
        return $key;
      }
    }

    //you should be able to use just uri - without preparing
    if (\crcl\base\resource\Utils::isValid($sIdent))
    {
      $key = \md5($sIdent);
      self::prepare($sIdent, $key);
      return $key;
    }

    throw new Exception(
      'Ident not found',
      Exception::ERROR_MULTITON_IDENTNOTFOUND,
      array('cache' => self::$aCache, 'class' => $sClass));
  }

  private static function getFactoryClass()
  {
    $child = \get_called_class();
    if (isset($child::$sFactoryClass))
    {
      return $child::$sFactoryClass;
    }
    return $child;
  }
}